package MaquinaVirtual;

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.*;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;


import Sintactico.*;
import interfaz.GUI;

/**
 * @author Grupo 11
 * @brief Representa la Maquina pila y el conjunto de metodos
 */
public class MaquinaPila{
	
	/*************** Atributos **************/	
	private NodoPila _cima;
	private int _cpila;
	private int _rs1;
	private int _rs2;
	private int _P;
	private int _cprog;
	public ArrayList<String> _prog; 
	private Memoria _mem;
	private AnalizadorSintactico _sintactico;
	private GUI _gui;
	String lectura;
	
	/**
	 * constructora
	 * @param gui
	 */
	public MaquinaPila( GUI gui) {
		_cpila		 = -1; //pila vacia
		_cprog 		 = 0;
		_rs1   		 = 0;
		_rs2   		 = 0;
		_P	   	 	 = 0;
		_cima  		 = null;
		_mem   		 = new Memoria();
		_prog  		 = new ArrayList<String>();
		_sintactico  = null;//
		_gui   		 = gui;
	}
	

	/**
	 * en la interfaz se llama a esta metodo a traves del obj maquina pila
	 * @param g
	 * @throws Exception 
	 */
	public boolean compilar(String g) throws Exception {
		_sintactico  = new AnalizadorSintactico();
		_sintactico.calcula(g);
		_gui.setTexTabErrores(_sintactico.getErrores());
		_gui.setTexTabSimb(_sintactico.getTS());
		_gui.setNumErrores(_sintactico.getNumErrores());
		return !_sintactico.getError();
	}
	
	/**
	 * Metodo que ejecuta codigo maquina pila
	 * NOTA: podemos hacer que este metodo ejecute un paso de la maquina pila
	 * y que el ejecutar todo sea un while de este metodo o hacer otro metodo que
	 * sea ejecutaPaso y este que ejecute todo del tiron.
	 * @param g
	 * @throws Exception 
	 * @throws Error_MV 
	 */
	
	public void ejecutar(String g) throws Error_MV, Exception {
		
		_cpila		 = -1; //pila vacia
		_cprog 		 = 0;
		_rs1   		 = 0;
		_rs2   		 = 0;
		_P	   	 	 = 0;
		_cima  		 = null;
		_mem   		 = new Memoria();
		_prog  		 = new ArrayList<String>();
				
		String cod =_sintactico.getCodigoP();
		_gui.setTexCod(cod);
		String[] inst = cod.split("\n");
		int cont = 0;
		
		while(_P!=1 && cont<inst.length){
			ejecutaEmite(inst[cont]);
			_gui.setTexPila(this.getPila());
			_gui.setTexMem(this._mem.getTabla());
			cont++;
		}
	}	
	
	public void ejecutarDebug(int x) throws Error_MV, Exception {
			
			if (x==0){
				
				_cpila		 = -1; //pila vacia
				_cprog 		 = 0;
				_rs1   		 = 0;
				_rs2   		 = 0;
				_P	   	 	 = 0;
				_cima  		 = null;
				_mem   		 = new Memoria();
				_prog  		 = new ArrayList<String>();
			}		
			String cod =_sintactico.getCodigoP();
			_gui.setTexCod(cod);
			String[] inst = cod.split("\n");
			
			if(_P!=1 /*&& cont<inst.length*/){
				ejecutaEmite(inst[x]);
				_gui.setTexPila(this.getPila());
				_gui.setTexMem(this._mem.getTabla());					
			}
		}	
	
	
	/**
	 * llama a cada instruccion de la maqPila leyendo lo q ha escrito el emite 
	 * @param instruc del .cod
	 * @throws Exception 
	 * @throws Error_MV 
	 */
	private void ejecutaEmite(String instruc) throws Error_MV, Exception {

		int aux = instruc.indexOf("(");
		int aux2 = instruc.indexOf(")");
		String param="";
		if(aux>-1 && aux2>-1){
			param = instruc.substring(aux+1, aux2);			

		}
		if(aux>-1){
		if(instruc.substring(0,aux).equals("apila")){
			apila(param);
		}
        else if(instruc.substring(0,aux).equals("apila_dir")){
        	int dir=Integer.parseInt(param);
        	apila_dir(dir);
        }
        else if(instruc.substring(0,aux).equals("desapila_dir")){
        	int dir=Integer.parseInt(param);
        	desapila_dir(dir);
        }
		}
        if (instruc.equals("menor")) menor();
        else if (instruc.equals("mayor")) mayor();
        else if (instruc.equals("menor_igual")) menor_igual();
        else if (instruc.equals("mayor_igual")) mayor_igual();
        else if (instruc.equals("igual")) igual();
        else if (instruc.equals("distinto")) distinto();
        else if (instruc.equals("suma")) suma();
        else if (instruc.equals("resta")) resta();
        else if (instruc.equals("multiplica")) multiplica();
        else if (instruc.equals("division")) divide();
        else if (instruc.equals("modulo")) modulo();
        else if (instruc.equals("despIzq")) despIzq();
        else if (instruc.equals("despDer")) despDer();
        else if (instruc.equals("and")) and();
        else if (instruc.equals("or")) or();
        else if (instruc.equals("cambioSigno")) cambioSigno();
        else if (instruc.equals("not")) not();
        else if (instruc.equals("converNat")) converNat();
        else if (instruc.equals("converInt")) converInt();
        else if (instruc.equals("converFloat")) converFloat();
        else if (instruc.equals("converChar")) converChar();
        else if (instruc.equals("out"))out();
        else if (instruc.equals("swap1")) swap1(); 
        else if (instruc.equals("swap2")) swap2();         	
        else if (instruc.equals("stop")) stopp();
        else if (instruc.compareTo("in")>0)	in(param);
	}
	
	
	/*************** Instrucciones propias **************/	
	
	/**
	 * Apila en la cima de la pila un daot
	 * @param dato dato que queremos apilar
	 */
	public void apila (String dato) {		
		_cima = new NodoPila(dato, _cima);
		_cpila++;
		_cprog++;
	}
	

	
	/**
	 * Desapila de la cima de la pila en la direccion de memoria indnicada
	 * @param dir direccion de memerio donde queremos desapilar
	 */
	public void apila_dir (int dir) {		
		_cima = new NodoPila(_mem.getMem().get(dir), _cima);
		_cpila++;
		_cprog++;
	}
	
	/**
	 * Desapila en una direccion de memoria la cima de la pila.
	 * @param dir direccion de memoria
 	 */
	public void desapila_dir (int dir) {		
		if (_cpila != -1) {
			if( dir < _mem.getMem().size() ){
				if ( _sintactico.getRefTS().getTipo(_sintactico.getRefTS().getIden(dir)) == VarCons.TipoVar.integer ||
					 _sintactico.getRefTS().getTipo(_sintactico.getRefTS().getIden(dir)) == VarCons.TipoVar.natural  ) {
					String strInt;
					try{
						 strInt =  Integer.toString( (int)Float.parseFloat(_cima.getDato() ));
					}
					catch(Exception e){	
						 strInt =  Integer.toString( (int)_cima.getDato().charAt(1) );
					}
					_mem.getMem().set(dir, strInt);
				}else {
					_mem.getMem().set(dir, _cima.getDato());
				}
			}
			else {
				_mem.getMem().add(dir, _cima.getDato());
			}
			_cima = _cima.getSig();
			_cpila--;
			_cprog++;
		}	
	}
	
	/**
	 * cambia en valor de el reg del swap1
	 */
	public void swap1(){		
		if (_rs1 == 0)
			_rs1 = 1;
		else 
			_rs1 = 0;
		_cprog++;
	}
	
	/**
	 * cambia en valor de el reg del swap2
	 */
	public void swap2() {		
		if (_rs2 == 0)
			_rs2 = 1;
		else 
			_rs2 = 0;
		_cprog++;
	}
	
	/**
	 * suma
	 */
	public void suma(){		
		if (_rs1 == 0){
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) + Float.parseFloat(_cima.getDato()) )) ;
			_cima = _cima.getSig();
		}
		else{
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) - Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();			
		}
		_cpila--;
		_cprog++;
	}
	
	/**
	 * resta
	 */
	public void resta() {		
		if (_rs1 == 0){
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) - Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();
		}
		else{
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) + Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();			
		}
		_cpila--;	
		_cprog++;
	}
	
	/**
	 * multiplicacion
	 */
	public void multiplica() {
		if (_rs2 == 0) {
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) * Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();
		}
		else {
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) / Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();		
		}
		_cpila--;
		_cprog++;
	}
	
	/**
	 * division
	 */
	public void divide() throws Error_MV{
		if (_rs2 == 0) {
			if ( Float.parseFloat(_cima.getDato()) == 0 )
				throw new Error_MV("RUNTIME ERROR: Division por cero.");
			
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) / Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();
		}
		else {
			_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) * Float.parseFloat(_cima.getDato() ))) ;
			_cima = _cima.getSig();		
		}
		_cpila--;
		_cprog++;
	}
	
	/**
	 * %
	 */
	public void modulo() {
		_cima.getSig().setDato( Float.toString(Float.parseFloat(_cima.getSig().getDato()) % Float.parseFloat(_cima.getDato() ))) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * >
	 */
	public void mayor() {
		int num;
		if ( Float.parseFloat(_cima.getSig().getDato())> Float.parseFloat(_cima.getDato() ))
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * <
	 */
	public void menor() {
		int num;
		if (  Float.parseFloat(_cima.getSig().getDato()) <  Float.parseFloat(_cima.getDato()) )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * ==
	 */
	public void igual() {
		int num;
		if (  Float.parseFloat(_cima.getSig().getDato()) ==  Float.parseFloat(_cima.getDato()) )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * !=
	 */
	public void distinto() {
		int num;
		if ( Float.parseFloat(_cima.getSig().getDato()) !=  Float.parseFloat(_cima.getDato() ))
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * >=
	 */
	public void mayor_igual() {
		int num;
		if (  Float.parseFloat(_cima.getSig().getDato()) >=  Float.parseFloat(_cima.getDato()) )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * <=
	 */
	public void menor_igual() {
		int num;
		if (  Float.parseFloat(_cima.getSig().getDato()) <=  Float.parseFloat(_cima.getDato()) )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * not
	 */
	public void not() {
		if (  Integer.parseInt(_cima.getDato()) == 1)
			_cima.setDato(String.valueOf(0));
		else
			_cima.setDato(String.valueOf(1));
		_cprog++;
	}
	
	/**
	 * or
	 */
	public void or() {
		int num;
		if (  Integer.parseInt(_cima.getDato()) == 1 || Integer.parseInt(_cima.getSig().getDato())==1  )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;		
	}
	
	/**
	 * and
	 */
	public void and() {
		int num;
		if ( Integer.parseInt(_cima.getDato()) == 1 && Integer.parseInt(_cima.getSig().getDato())==1 )
			num = 1;
		else
			num = 0;
		_cima.getSig().setDato(String.valueOf(num)) ;
		_cima = _cima.getSig();
		_cpila--;
		_cprog++;
	}
	
	/**
	 * guardo en mem lo que me entra desde consola
	 * @param dir de mem donde guardo la var
	 * @throws IOException
	 * @throws Errores_maquina_pila
	 * @throws InterruptedException
	 */
	public void in( String dir) throws Exception, Error_MV {
		/*
		 * Comprobamos el tipo que leemos con el de 
		 * la variable de la tabla se simbolos.
		 */
		String id="";
		if(!dir.equals("")){
			id=this._sintactico.getRefTS().getIden(Integer.parseInt(dir));
		}
		String valor="";
		lectura="";
		lectura=JOptionPane.showInputDialog("lectura variable: "+id);
		valor=lectura;
		lectura+=" ";
		if(_sintactico.leer(dir, lectura)){
			if ( _sintactico.getRefTS().getTipo(id).equals(VarCons.TipoVar.floAt))
				valor = Float.toString(Float.parseFloat(valor));
			if (valor.equals("true"))
				valor = "1";
			else if  (valor.equals("false"))
				valor = "0";
			_mem.getMem().set(Integer.parseInt(dir),valor);
		}else{
			this._gui.setTexTabErrores("Error de lectura.");
			throw new Error_MV("RUNTIME ERROR: El valor introducido no corresponde con el tipo de la variable " + id);
		}
		_cprog++;
	}
		
	
	/**
	 * escribo por consola un dato de memoria
	 */
	public void out() {
		_gui.setTextTabConsola(_cima.getDato());
		_cpila--;
		_cima = _cima.getSig();
		_cprog++;
	}
	
	/**
	 * despIzq
	 */
	public void despIzq() {
		int num = Integer.parseInt(_cima.getSig().getDato())<< Integer.parseInt(_cima.getDato());
		_cima = _cima.getSig();
		_cima.setDato(String.valueOf(num));
		_cpila--;
		_cprog++;
	}
	
	/**
	 * despDer
	 */
	public void despDer() {
		int num = Integer.parseInt(_cima.getSig().getDato()) >> Integer.parseInt(_cima.getDato());
		_cima = _cima.getSig();
		_cima.setDato(String.valueOf(num));
		_cpila--;
		_cprog++;
	}
	
	/**
	 * castingFloat
	 */
	public void converFloat() {
		try {
			_cima.setDato( Float.toString( Float.parseFloat( _cima.getDato()) ) );
		}
		catch (NumberFormatException e){
			
			char[] str = _cima.getDato().toCharArray();
			char car = str[0];
			String ascii = Integer.toString( (int)car );
			_cima.setDato( Float.toString( Float.parseFloat( ascii ))  );
		}
		_cprog++;
	}
	
	/**
	 * casting Enteros
	 */
	public void converInt() {
		try{
			_cima.setDato( Integer.toString( (int)Float.parseFloat(_cima.getDato() )));
		}
		catch(Exception e){	
			_cima.setDato( Integer.toString( (int)_cima.getDato().charAt(1) ));
		}
		_cprog++;
	}
	
	/**
	 * casting Naturales
	 */ 
	public void converNat() {
		try {
			_cima.setDato( Integer.toString( Integer.parseInt( _cima.getDato()) ));
		}
		catch(NumberFormatException e){
			_cima.setDato( Integer.toString( (int)_cima.getDato().charAt(1)));
		}
		_cprog++;
	}
	
	/**
	 * castingChar
	 * @throws Error_MV 
	 */
	public void converChar() throws Error_MV{
		String car;
		try {
			car= "" + (char)Integer.parseInt(_cima.getDato());
		}
		catch (java.lang.NumberFormatException e) {
			car = _cima.getDato();
		} 
		_cima.setDato( car );
		_cprog++;
	}
	
	/**
	 * Cambio de signo
	 * */
	public void cambioSigno(){
		_cima.setDato( Float.toString( Float.parseFloat( _cima.getDato() ) * -1 ));
		_cprog++;
	}
	
	/**
	 * Activa el registro de parada de ejecucion
	 * */
	public void stopp(){
		_P = 1;
	}
	
	/**
	 * 
	 * @return el dato de la cima de la pila
	 */
	public String cima(){
		return _cima.getDato();
	}
	
	/********* funciones aux ************/
	public String getPila(){
		String pila="";
		NodoPila nodo=this._cima;
		for(int i=this._cpila;i>=0;i--){
			pila=pila+nodo.getDato()+"\n";
			nodo=nodo.getSig();
		}
		
		return pila;
	}
	
	public int getP(){
			return _P;
	}
	
	 public String getListErrores() {
		 return _sintactico.getErrores();
	 }
	
}
